<?php

/**
* xml-rpc value element wrapper
* @author J.Ducastel <fredbird@fredbird.org>
* @version
*/
class ooXmlRpcValue {
/*------------------------------------------------------------------------------
                                                                     constructor
------------------------------------------------------------------------------*/
    /**
    *
    * @access
    */
    function ooXmlRpcValue($type=null,$value=null) {
        $this->__construct($type,$value);
    }/**/

    /**
    * "real" constructor. Sets type and value if provided
    * @param string $type
    * @param mixed $value
    * @access public
    */
    function __construct($type=null,$value=null) {
        $this->setType($type);
        $this->setValue($value);
    }/**/
/*------------------------------------------------------------------------------
                                                                  public methods
------------------------------------------------------------------------------*/
    /**
    * sets value from ooXmlRpcNode instance
    * @param ooxmlrpcnode &$node
    * @access public
    * @return bool
    */
    function setFromNode(&$node) {
        // checking input
        if (!is_a($node,'ooxmlrpcdomnode')) {
            return false;
        }
        //var_dump($node);
        // searching for type = value child element tagname
        if ($typeNodes=$node->getxpathnodes('child::*')) {
            $type=$typeNodes[0]->getTagName();
            $valueXpath='child::*';
            //$valueNodes=$node->getXpathNodes('child::*');
        } else {
            // no child element, default to string
            $type='string';
            $valueXpath='text()';
            //$valueNodes=$node->getXpathNodes('text()');
        }
        $this->type=$type;
        // getting value content node(s)
        $valueNodes=$node->getXpathNodes($valueXpath);
        // extracting value, method depends on type
        switch($type) {
            // non-scalars
            case 'array':
                $this->value=array();
                // sub-values are within <data>
                $valueNodes=$valueNodes[0]->getXpathNodes('data/*');
                // cycling values
                for ($n=0; $n<count($valueNodes); $n++) {
                    // building instance
                    $value=&new ooXmlRpcValue();
                    // setting from node
                    $value->setFromNode($valueNodes[$n]);
                    // importing instance
                    $this->value[]=&$value;
                }
                break;
            case 'struct':
                $this->value=array();
                // sub-values are within <data>
                $valueNodes=$valueNodes[0]->getXpathNodes('member');
                // cycling values (<member> nodes)
                for ($n=0; $n<count($valueNodes); $n++) {
                    // building instance
                    $value=&new ooXmlRpcValue();
                    // getting key
                    $subKeyNodes=$valueNodes[$n]->getXpathNodes('name');
                    $key=$subKeyNodes[0]->getContent();
                    // getting value
                    $subValueNodes=$valueNodes[$n]->getXpathNodes('value');
                    // setting from node
                    $value->setFromNode($subValueNodes[0]);
                    // importing instance
                    $this->value[$key]=&$value;
                }
                break;
            // scalars
            case 'boolean':
                $value=$valueNodes[0]->getContent();
                if ($value=='1' or strtolower($value)=='true')
                    $this->value=true;
                else $this->value=false;
                break;
            case 'i4':
            case 'int':
            case 'integer':
                $value=$valueNodes[0]->getContent();
                $this->type='int';
                $this->value=intval($value);
                break;
            case 'double':
                $value=$valueNodes[0]->getContent();
                //$this->type='int';
                settype($value,'double');
                $this->value=&$value;
                break;
            case 'string':
                //$value=$valueNodes[0]->getContent();
                //$this->value=&$value;
                //break;
            case 'base64':
                //$value=$valueNodes[0]->getContent();
                //$this->value=base64_decode($value);
                //$this->value=&$value;
                //break;
            case 'dateTime.iso8601':
                //$value=$valueNodes[0]->getContent();
                //break;
            default: // scalar
                $this->value=$valueNodes[0]->getContent();
        }
        return true;
        //var_dump($valueNodes);
    }/**/

    /**
    * check/tells if the value is valid
    * @param mixed $value optional, checks current value if omitted
    * @access public
    * @return bool
    */
    function isValid($value=null) {
        if (is_null($value))
            $value=&$this->value;
        switch($this->type) {
            // scalars
            case 'boolean':
                return in_array($value,array(0,1));
            case 'string':
                return is_string($value);
            case 'int':
                return is_int($value);
            case 'double':
                return is_double($value);
            case 'base64':
                return ereg('[a-zA-Z0-9+=/]+',$value);
            case 'dateTime.iso8601':
                return ereg('[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}',$value);
            // non-scalars
            case 'array':
                return is_array($value);
            case 'struct':
                return is_array($value) or is_object($value);
            default:
                return false;
        }
    }/**/

    /**
    * check/tells if value is a scalar
    * @param mixed $value optional, checks current value if not provided
    * @access public
    * @return
    */
    function isScalar($value=null) {
        if (is_null($value))
            $value=&$this->value;
        return is_scalar($value);
    }/**/

    /**
    * return value's type
    * @access public
    * @return string int|string|boolean|base64|double|dateTime.iso8601|struct|array
    */
    function getType() {
        return $this->type;
    }/**/

    /**
    * sets type (value has to be null)
    * @param string $type
    * @access public
    * @return bool
    */
    function setType($type) {
        if (!in_array($type,array('string','int','double','boolean','base64','dateTime.iso8601','struct','array'))) {
            return false;
        }
        if (!is_null($this->value)) {
            return false;
        }
        $this->type=$type;
        return true;
    }/**/

    /**
    * sets value (type has to be set first, else it will be guessed
    * @param mixed $value
    * @access public
    * @return bool
    */
    function setValue($value) {
        if (is_null($value))
            return false;
        // if type is not set, try guessing
        if (is_null($this->type) and $guessed=$this->guessType($value)) {
            // echo "<p>guessed $guessed</p>";
            $this->setType($guessed);
        }
        if (!$this->isValid($value))
            return false;
        // scalar
        // $this->value=&$value;
        switch ($this->type) {
            // non-scalars
            case 'array':
            case 'struct':
                if (is_object($value))
                    $value=get_object_vars($value);
                if (!is_array($value))
                    return false;
                foreach ($value as $key=>$item) {
                    if (is_a($item,'ooxmlrpcvalue')) {
                        $this->value[$key]=$item;
                    } else {
                        $subValue=&new ooXmlRpcValue(null,$item);
                        if ($subValue->setValue($item))
                            $this->value[$key]=&$subValue;
                    }
                }
                break;
            // scalars
            case 'boolean':
                if ($value) $this->value=1;
                else $this->value=0;
                break;
            case 'int':
            case 'double':
            case 'base64':
            case 'string':
            case 'dateTime.iso8601':
                $this->value=&$value;
                break;
            default:
                return false;
        }

        return true;
    }/**/

    /**
    * build and append self node(s) to parent node
    * @param ooXmlRpcDomNode &$parentNode
    * @access public
    * @return bool
    */
    function appendToNode(& $parentNode) {
        // build & append value node
        $valueNode=&$parentNode->makeChild('value');
        // build & append type node to value node
        $typeNode=&$valueNode->makeChild($this->type);
        // insert content into type node
        switch ($this->type) {
            // non-scalars
            case 'array':
                //$arrayNode=&$typeNode->makeChild('array');
                // append data node
                $dataNode=&$typeNode->makeChild('data');
                // append child values to data node
                for ($n=0; $n<count($this->value); $n++) {
                    $this->value[$n]->appendToNode($dataNode);
                }
                break;
            case 'struct':
                // cycling through sub-value nodes
                if (!is_array($this->value)) return false;
                foreach ($this->value as $key=>$value) {
                    // appending <member> nodes
                    $memberNode=&$typeNode->makeChild('member');
                    // appending <name>
                    $nameNode=&$memberNode->makeChild('name');
                    $nameNode->setContent($key);
                    // appending <value>
                    //$deb=&wak::getservice('wakdebug');
                    //echo $deb->vardump($value);
                    $value->appendToNode($memberNode);
                }
                break;
            //scalars
            default:
                $typeNode->setContent($this->value);
        }
        return true;
    }/**/

    /**
    * tries to guess a xml-rpc type for the var
    * @param
    * @access public
    * @return
    */
    function guessType(&$var) {
        if (is_bool($var))
            return 'boolean';
        if (is_int($var))
            return 'int';
        if (is_double($var))
            return 'double';
        if (is_object($var))
            return 'struct';
        if (is_array($var)) {
            if (is_int(key($var)))
                return 'array';
            else return 'struct';
        }
        if (is_string($var) and ereg('[0-9]{6}T[0-9]{2}:[0-9]{2}:[0-9]{2}',$var))
            return 'dateTime.iso8601';
        if (is_string($var) and ereg('^[a-zA-Z0-9+/=]{50,}$',$var))
            return 'base64';
        if (is_string($var))
            return 'string';
        
        return false;
    }/**/

    /**
    * Return value
    * @access public
    * @return mixed
    */
    function getValue() {
        if (!is_array($this->value))
                return $this->value;
        // recursion to get sub-values
        $value=array();
        foreach (array_keys($this->value) as $key) {
            $value[$key]=$this->value[$key]->getValue();
        }
        return $value;
    }/**/

    /**
    *
    * @param
    * @access public
    * @return
    * /
    function () {

    }/**/
/*------------------------------------------------------------------------------
                                                                      properties
------------------------------------------------------------------------------*/
    /**
    * @var type desc
    */
    var $type; // int|boolean|string|base64|dateTime.iso8601|struct|array
    var $value;
}
?>
